// 观察者(发布订阅)

class Dep{
    constructor() {
        this.subs = [];
    }
    addSub(watcher) {
        this.subs.push(watcher);
    }
    nodetify() {
        this.subs.forEach(watcher => watcher.update());
    }
}

class Watcher{
    constructor(vm, expr, cb) {
        this.vm = vm;
        this.expr = expr;
        this.cb = cb;
        this.oldValue = this.get();
    }
    get() {
        Dep.target = this;
        let value = ComplieUtil.getValue(this.vm, this.expr)
        Dep.target = null;
        return value;
    }
    update() {
        let newValue = ComplieUtil.getValue(this.vm, this.expr);
        if (newValue !== this.oldValue) {
            this.cb(newValue);
        }
    }
}

class Observer{ // 实现数据劫持
    constructor(data) {
        this.observer(data);
    }
    observer(data) {
        if (data && typeof data === 'object') {
            for(let key in data) {
                this.defineReactive(data, key, data[key]);
            }
        }
    }
    defineReactive(obj, key, value) {
        this.observer(value);
        let dep = new Dep();
        Object.defineProperty(obj, key, {
            get() {
                Dep.target && dep.addSub(Dep.target);
                return value;
            },
            set:(newValue) => {
                if (value !== newValue) {
                    this.observer(newValue);
                    value = newValue;
                    dep.nodetify();
                }
            }
        })
    }
}

class Complier{
    constructor(el, vm){
        this.el = this.isElementNode(el) ? el : document.querySelector(el);
        this.vm = vm;
        let fragment = this.node2Fragment(this.el);
        // 节点替换
        // 编译模板
        this.compile(fragment);
        // 吧内容放回页面中
        this.el.appendChild(fragment);
    }
    isElementNode(node) {
        return node.nodeType === 1;
    }
    isDirective(attrName) {
        return attrName.startsWith("v-");
    }
    compileElement(node) {
        let attributes = node.attributes;
        [...attributes].forEach(attr => {
            let {name, value:expr} = attr;
            if (this.isDirective(name)) {
                // node, expr, vm
                ComplieUtil['model'](node, expr, this.vm);
            }
        })
    }
    compileText(node) {
        let content = node.textContent;
        if (/\{\{(.+?)\}\}/.test(content)) {
            ComplieUtil['text'](node, content, this.vm);
        }
        
    }
    node2Fragment(node) {
        let fragment = document.createDocumentFragment();
        let child;
        while (child = node.firstChild) {
            fragment.appendChild(child);
        }
        return fragment;
    }
    compile(node) {
        let childNodes = node.childNodes;   
        [...childNodes].forEach(item => {
            if (this.isElementNode(item)) {
                this.compileElement(item);
                // 递归编译子节点
                this.compile(item);
            } else {
                this.compileText(item);
            }
        })
    }
}

ComplieUtil = {
    // vm.$data 包含数据  expr: 'school.name'
    getValue(vm, expr) {
        return expr.split('.').reduce((data, current)=>{
            return data[current];
        }, vm.$data);
    },
    setValue(vm, expr, value) {
        expr.split('.').reduce((data, current, index, arr)=>{
            if (index === arr.length - 1) {
                data[current] = value;
            }
            return data[current];
        }, vm.$data);
    },
    model(node, expr, vm) {
        let fn = this.updater['modelUpdater'];
        new Watcher(vm, expr, (newValue) => {
            fn(node, newValue);
        });
        let value = this.getValue(vm , expr);
        node.addEventListener('input', (e) => {
            let value = e.target.value;
            this.setValue(vm, expr, value)
        })
        fn(node, value);
    },
    getContentValue(vm, expr) {
        return expr.replace(/\{\{(.+?)\}\}/g, (...args)=> {
            return this.getValue(vm, args[1])
        });
    },
    text(node, expr, vm) {
        let fn = this.updater['textUpdate'];
        let content = expr.replace(/\{\{(.+?)\}\}/g, (...args)=> {
            new Watcher(vm , args[1], () => {
                fn(node, this.getContentValue(vm, expr));
            });
            return this.getValue(vm, args[1])
        });
        fn(node, content);
    },
    updater: {
        modelUpdater(node, value) {
            node.value = value;
        },
        textUpdate(node, value) {
            node.textContent = value;  
        }
    }
}

class Vue{
    constructor(options) {
        this.$el = options.el;
        this.$data = options.data;
        let computed = options.computed;
        if (this.$el) {
            new Observer(this.$data);
            
            for(let key in computed) {
                Object.defineProperty(this.$data, key, {
                    get: () => {
                        return computed[key].call(this.$data);
                    }
                })
            }
            new Complier(this.$el, this);
        }
    }
}